/**
 * Copyright 2010-present Facebook.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.internal;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
import com.facebook.Session;
import com.facebook.SessionState;

/**
 * com.facebook.internal is solely for the use of other packages within the Facebook SDK for Android. Use of
 * any of the classes in this package is unsupported, and they may be modified or removed without warning at
 * any time.
 */
public class SessionTracker
{

  private Session session;
  private final Session.StatusCallback callback;
  private final BroadcastReceiver receiver;
  private final LocalBroadcastManager broadcastManager;
  private boolean isTracking = false;

  /**
   * Constructs a SessionTracker to track the active Session object.
   *
   * @param context  the context object.
   * @param callback the callback to use whenever the active Session's
   *                 state changes
   */
  public SessionTracker(Context context, Session.StatusCallback callback)
  {
    this(context, callback, null);
  }

  /**
   * Constructs a SessionTracker to track the Session object passed in.
   * If the Session is null, then it will track the active Session instead.
   *
   * @param context  the context object.
   * @param callback the callback to use whenever the Session's state changes
   * @param session  the Session object to track
   */
  SessionTracker(Context context, Session.StatusCallback callback, Session session)
  {
    this(context, callback, session, true);
  }

  /**
   * Constructs a SessionTracker to track the Session object passed in.
   * If the Session is null, then it will track the active Session instead.
   *
   * @param context       the context object.
   * @param callback      the callback to use whenever the Session's state changes
   * @param session       the Session object to track
   * @param startTracking whether to start tracking the Session right away
   */
  public SessionTracker(Context context, Session.StatusCallback callback, Session session, boolean startTracking)
  {
    this.callback = new CallbackWrapper(callback);
    this.session = session;
    this.receiver = new ActiveSessionBroadcastReceiver();
    this.broadcastManager = LocalBroadcastManager.getInstance(context);

    if (startTracking)
    {
      startTracking();
    }
  }

  /**
   * Returns the current Session that's being tracked.
   *
   * @return the current Session associated with this tracker
   */
  public Session getSession()
  {
    return (session == null) ? Session.getActiveSession() : session;
  }

  /**
   * Returns the current Session that's being tracked if it's open,
   * otherwise returns null.
   *
   * @return the current Session if it's open, otherwise returns null
   */
  public Session getOpenSession()
  {
    Session openSession = getSession();
    if (openSession != null && openSession.isOpened())
    {
      return openSession;
    }
    return null;
  }

  /**
   * Set the Session object to track.
   *
   * @param newSession the new Session object to track
   */
  public void setSession(Session newSession)
  {
    if (newSession == null)
    {
      if (session != null)
      {
        // We're current tracking a Session. Remove the callback
        // and start tracking the active Session.
        session.removeCallback(callback);
        session = null;
        addBroadcastReceiver();
        if (getSession() != null)
        {
          getSession().addCallback(callback);
        }
      }
    }
    else
    {
      if (session == null)
      {
        // We're currently tracking the active Session, but will be
        // switching to tracking a different Session object.
        Session activeSession = Session.getActiveSession();
        if (activeSession != null)
        {
          activeSession.removeCallback(callback);
        }
        broadcastManager.unregisterReceiver(receiver);
      }
      else
      {
        // We're currently tracking a Session, but are now switching
        // to a new Session, so we remove the callback from the old
        // Session, and add it to the new one.
        session.removeCallback(callback);
      }
      session = newSession;
      session.addCallback(callback);
    }
  }

  /**
   * Start tracking the Session (either active or the one given).
   */
  public void startTracking()
  {
    if (isTracking)
    {
      return;
    }
    if (this.session == null)
    {
      addBroadcastReceiver();
    }
    // if the session is not null, then add the callback to it right away
    if (getSession() != null)
    {
      getSession().addCallback(callback);
    }
    isTracking = true;
  }

  /**
   * Stop tracking the Session and remove any callbacks attached
   * to those sessions.
   */
  public void stopTracking()
  {
    if (!isTracking)
    {
      return;
    }
    Session session = getSession();
    if (session != null)
    {
      session.removeCallback(callback);
    }
    broadcastManager.unregisterReceiver(receiver);
    isTracking = false;
  }

  /**
   * Returns whether it's currently tracking the Session.
   *
   * @return true if currently tracking the Session
   */
  public boolean isTracking()
  {
    return isTracking;
  }

  /**
   * Returns whether it's currently tracking the active Session.
   *
   * @return true if the currently tracked session is the active Session.
   */
  public boolean isTrackingActiveSession()
  {
    return session == null;
  }

  private void addBroadcastReceiver()
  {
    IntentFilter filter = new IntentFilter();
    filter.addAction(Session.ACTION_ACTIVE_SESSION_SET);
    filter.addAction(Session.ACTION_ACTIVE_SESSION_UNSET);

    // Add a broadcast receiver to listen to when the active Session
    // is set or unset, and add/remove our callback as appropriate
    broadcastManager.registerReceiver(receiver, filter);
  }

  /**
   * The BroadcastReceiver implementation that either adds or removes the callback
   * from the active Session object as it's SET or UNSET.
   */
  private class ActiveSessionBroadcastReceiver extends BroadcastReceiver
  {
    @Override
    public void onReceive(Context context, Intent intent)
    {
      if (Session.ACTION_ACTIVE_SESSION_SET.equals(intent.getAction()))
      {
        Session session = Session.getActiveSession();
        if (session != null)
        {
          session.addCallback(SessionTracker.this.callback);
        }
      }
    }
  }

  private class CallbackWrapper implements Session.StatusCallback
  {

    private final Session.StatusCallback wrapped;

    public CallbackWrapper(Session.StatusCallback wrapped)
    {
      this.wrapped = wrapped;
    }

    @Override
    public void call(Session session, SessionState state, Exception exception)
    {
      if (wrapped != null && isTracking())
      {
        wrapped.call(session, state, exception);
      }
      // if we're not tracking the Active Session, and the current session
      // is closed, then start tracking the Active Session.
      if (session == SessionTracker.this.session && state.isClosed())
      {
        setSession(null);
      }
    }
  }
}
